#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/reboot.h>
#include <linux/pwm.h>
#include <linux/gpio.h>
#include <linux/hrtimer.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>

#include "gpio_clk_driver.h"


struct virt_gpio_clk_gate
{
	struct gpio_desc *gpiod_clk_gate;
	struct clk_hw	hw;	
};

#define to_clk_gpio(_hw) container_of(_hw, struct virt_gpio_clk_gate, hw)

static int virt_gpio_clk_gate_enable(struct clk_hw *hw)
{
	struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);

	gpiod_set_value(gpio_clk_gate_ptr->gpiod_clk_gate, 1);
	return 0;
}

static void virt_gpio_clk_gate_disable(struct clk_hw *hw)
{
	struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);
	gpiod_set_value(gpio_clk_gate_ptr->gpiod_clk_gate, 0);
}

static int virt_gpio_clk_gate_is_enabled(struct clk_hw *hw)
{
	struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);
	
	return gpiod_get_value(gpio_clk_gate_ptr->gpiod_clk_gate);
}

static struct clk_ops virt_gpio_clk_gate_ops =
{
	.enable = virt_gpio_clk_gate_enable,
	.disable = virt_gpio_clk_gate_disable,
	.is_enabled = virt_gpio_clk_gate_is_enabled,

};

static int virt_gpio_clk_gate_probe(struct platform_device *pdev)
{
	struct gpio_clk_gate_platform_data *pdata = (struct gpio_clk_gate_platform_data *)(pdev->dev.platform_data);
	struct virt_gpio_clk_gate * gpio_clk_gate_ptr = NULL;
	struct clk_init_data init_data;
	unsigned long gate_gpio_flag = 0;
	struct clk *clk;
	int ret = 0;
	#ifdef CONFIG_OF
	int en_gpio = 0;
	enum of_gpio_flags of_flags;
	#endif

	printk("%s %d\n", __FUNCTION__, __LINE__);
	gpio_clk_gate_ptr = devm_kzalloc(&pdev->dev, sizeof(struct virt_gpio_clk_gate), GFP_KERNEL);
		
	if (!gpio_clk_gate_ptr)
		return -ENOMEM;

	printk("%s %d\n", __FUNCTION__, __LINE__);
	memset(&init_data, 0, sizeof(init_data));

	init_data.num_parents = 0;
	init_data.ops = &virt_gpio_clk_gate_ops;
	init_data.name = pdev->name;
	init_data.flags = CLK_IS_ROOT;
	
	if(pdata != NULL)
	{
		gpio_clk_gate_ptr->gpiod_clk_gate = gpio_to_desc(pdata->enable_gpio_index);
		gpio_clk_gate_ptr->hw.init = &init_data;

		printk("%s %d\n", __FUNCTION__, __LINE__);
		if(pdata->active_low)
			gate_gpio_flag = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_HIGH;
		else
			gate_gpio_flag = GPIOF_OUT_INIT_LOW;
	}

	#ifdef CONFIG_OF	
	if(pdev->dev.of_node != NULL)
	{
		en_gpio = of_get_named_gpio_flags(pdev->dev.of_node, "enable-gpio", 0, &of_flags);
		if(en_gpio < 0)
			return en_gpio;

		if(of_flags & OF_GPIO_ACTIVE_LOW)
			gate_gpio_flag = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_HIGH;
		else
			gate_gpio_flag = GPIOF_OUT_INIT_LOW;

	}
	#endif

	printk("%s %d gpio index=%d\n", __FUNCTION__, __LINE__, pdata->enable_gpio_index);
	ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio_index, gate_gpio_flag, pdev->name);
	if (ret) 
	{
		printk("%s %d ret=%d\n", __FUNCTION__, __LINE__, ret);
		return ret;
	}

	printk("%s %d\n", __FUNCTION__, __LINE__);
	clk = devm_clk_register(&pdev->dev, &gpio_clk_gate_ptr->hw);
	if (IS_ERR(clk))
		return -EINVAL;

	printk("%s %d\n", __FUNCTION__, __LINE__);
	if(pdev->dev.of_node != NULL)
		return of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
	else 
		return clk_register_clkdev(clk, "virt_gpio_gate", NULL);
	
	printk("%s %d\n", __FUNCTION__, __LINE__);
}

static int virt_gpio_clk_gate_remove(struct platform_device *pdev)
{


	printk("%s %d\n", __FUNCTION__, __LINE__);
	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id of_gpio_clk_gate_match[] = {
	{ .compatible = "virt_gpio_clk", },
	{},
};
#endif
static struct platform_driver gpio_clk_gate_driver = {
	.probe		= virt_gpio_clk_gate_probe,
	.remove		= virt_gpio_clk_gate_remove,
	.driver		= {
		.name	= "virt_gpio_clk",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(of_gpio_clk_gate_match),
	},
};

module_platform_driver(gpio_clk_gate_driver);

MODULE_AUTHOR("jerry_chg");
MODULE_DESCRIPTION("gpio clk gate driver");
MODULE_LICENSE("GPL");
